﻿using System.Collections;
using System.Collections.Immutable;
using System.Diagnostics;
using DimensionsHelper.Common.Utils;
using DimensionsHelper.LanguageService.Protocol;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService.Symbols;

/// <summary>
///     语法符号的基类。此类是抽象类，无法实例化。
/// </summary>
[DebuggerDisplay("{FullName,nq}")]
public abstract class AbstractSymbol : ISymbol
{
    protected readonly SymbolLocationMap LocationMap;

    protected internal AbstractSymbol(SymbolKinds kind)
    {
        Kind = kind;
        LocationMap = new SymbolLocationMap(this);
    }


    /// <summary>
    ///     如果此符号允许被声明为数组类型，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool CanMakeArray => false;


    /// <summary>
    ///     符号类型。
    /// </summary>
    public SymbolKinds Kind { get; protected init; }

    /// <summary>
    ///     符号名。
    /// </summary>
    public virtual string Name => "<Unknown>";

    /// <summary>
    ///     符号全名，一般情况下和 <see cref="Name" /> 相同。
    /// </summary>
    public virtual string FullName
    {
        get
        {
            string full = Name;
            if (ContainingSymbol != null)
            {
                full = ContainingSymbol.FullName + "." + full;
            }

            return full;
        }
    }

    /// <summary>
    ///     符号的原始定义，一般情况下和此符号相同。
    /// </summary>
    public virtual ISymbol OriginalDefinition => this;


    /// <summary>
    ///     包含此符号的符号定义，类型定义、类成员定义、命名空间定义符号的
    ///     此属性可能是<see langword="null" />
    /// </summary>
    public virtual ISymbol? ContainingSymbol => null;


    /// <summary>
    ///     如果此符号是未定义的符号，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsUndefined => Kind == SymbolKinds.Undefined;


    /// <summary>
    ///     如果此符号是宏定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsMacro => false;


    /// <summary>
    ///     如果此符号是本地变量定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsLocalVariable => Kind == SymbolKinds.Variable;


    /// <summary>
    ///     如果此符号是本地数组定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsArray => Kind == SymbolKinds.Array;


    /// <summary>
    ///     如果此符号是数组类型，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsArrayType => Kind == SymbolKinds.ArrayType;


    /// <summary>
    ///     如果此符号是值类型符号，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsValueType => false;


    /// <summary>
    ///     如果此符号是对象类型符号，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsObjectType => false;


    /// <summary>
    ///     如果此符号是常量定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsConstant => Kind == SymbolKinds.Constant;


    /// <summary>
    ///     如果此符号是函数定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsFunction => false;


    /// <summary>
    ///     如果此符号是属性定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsProperty => false;


    /// <summary>
    ///     如果此符号是成员方法定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsMethod => false;


    /// <summary>
    ///     如果此符号是参数定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsParameter => false;


    /// <summary>
    ///     如果此符号是元数据定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsMetadata => false;


    public virtual bool IsMetadataField => false;


    public bool IsEvent => Kind == SymbolKinds.EventSection;


    /// <summary>
    ///     如果此符号是元数据分类定义，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool IsMetadataElement => false;


    /// <summary>
    ///     如果此符号允许被继承，此属性是<see langword="true" />。
    /// </summary>
    public virtual bool CanBeDerived => false;


    /// <summary>
    ///     获取声明此符号的所有文本位置。
    /// </summary>
    /// <returns>声明此符号的文本位置列表，一般情况下列表长度是1。</returns>
    public ImmutableArray<SyntaxLocation> GetLocations()
    {
        return LocationMap.GetLocations();
    }


    /// <summary>
    ///     获取第一个声明此符号的文本位置。
    /// </summary>
    /// <returns>第一个声明此符号的文本位置。</returns>
    /// <exception cref="Exception" />
    public SyntaxLocation GetFirstLocation()
    {
        return LocationMap.Count > 0
            ? LocationMap[0]
            : throw new Exception("Symbol has no location.");
    }


    /// <summary>
    ///     获取在指定文件中的此符号的定义位置列表。
    /// </summary>
    /// <param name="fileName">文件名</param>
    /// <returns>定义列表，如果在该文件中没有定义，返回空列表</returns>
    public ImmutableArray<SyntaxLocation> GetLocations(string fileName)
    {
        return LocationMap.GetLocationsOfFile(fileName);
    }


    /// <summary>
    ///     重新声明符号。
    /// </summary>
    /// <param name="symbol">要声明的符号定义</param>
    /// <returns>如果声明失败，返回<see langword="false" /></returns>
    public virtual bool Redeclare(ISymbol symbol)
    {
        return false;
    }


    public virtual void Reference(in SyntaxLocation location)
    {
        LocationMap.AddLocation(location);
    }


    public void RemoveReference(in SyntaxLocation location)
    {
        LocationMap.RemoveLocation(location);
    }


    public virtual bool IsDeclared(string fileName, int position)
    {
        return !LocationMap.IsDeActiveAtPositionOfFile(fileName, position);
    }


    public virtual bool IsDeclared(DocumentUri uri, int position)
    {
        return !LocationMap.IsDeActiveAtPositionOfFile(uri, position);
    }


    public virtual bool IsDeclared(string fileName)
    {
        return !LocationMap.IsDeActiveInFile(fileName);
    }


    public virtual bool IsDeclared(DocumentUri uri)
    {
        return !LocationMap.IsDeActiveInFile(uri);
    }


    /// <summary>
    ///     获取指定文件中此符号定义的位置列表。
    /// </summary>
    /// <param name="uri">文件URI</param>
    /// <returns>定义列表，如果该文件中没有定义此符号，返回空列表</returns>
    public ImmutableArray<SyntaxLocation> GetLocations(DocumentUri uri)
    {
        return LocationMap.GetLocationsOfFile(uri);
    }


    [DebuggerDisplay("Count = {Count} [{Uri.ToString(),nq}]")]
    private class FileLocationIndices(DocumentUri uri, List<SyntaxLocation> locations) : IReadOnlyList<int>
    {
        private readonly List<int> _locIndices = [];
        private int _deactivateLocations;

        public bool ContainsDeactivate => _deactivateLocations > 0;

        public DocumentUri Uri { get; } = uri;

        public int this[int index] => index >= 0 && index < _locIndices.Count
            ? _locIndices[index]
            : throw new IndexOutOfRangeException(nameof(index));

        public int Count => _locIndices.Count;

        public IEnumerator<int> GetEnumerator()
        {
            return _locIndices.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public void Add(in SyntaxLocation location)
        {
            if (!location.IsActive)
            {
                _deactivateLocations++;
            }

            int placement = locations.Count;

            if (_locIndices.Count == 0)
            {
                _locIndices.Add(placement);
            }
            else
            {
                TextSpan locSpan = location.Span;
                if (locations[_locIndices[0]].Span > locSpan)
                {
                    _locIndices.Insert(0, placement);
                }
                else if (locations[_locIndices[^1]].Span < locSpan)
                {
                    _locIndices.Add(placement);
                }
                else
                {
                    int findIndex = _locIndices.FindIndex(li => locations[li].Span >= locSpan);
                    if (findIndex != -1)
                    {
                        int locIndex = _locIndices[findIndex];
                        if (locSpan == locations[locIndex].Span)
                        {
                            locations[locIndex] = location;
                            return;
                        }

                        _locIndices.Insert(findIndex, placement);
                    }
                    else
                    {
                        _locIndices.Add(placement);
                    }
                }
            }

            locations.Add(location);
        }


        public ImmutableArray<SyntaxLocation> GetLocations()
        {
            return [.. _locIndices.Select(li => locations[li])];
        }


        /// <summary>
        /// 将指定的语法位置移除
        /// </summary>
        /// <param name="location">语法位置</param>
        public void Remove(SyntaxLocation location)
        {
            int index = _locIndices.FindIndex(loc => locations[loc] == location);
            if (index < 0)
            {
                return;
            }

            int locationIndex = _locIndices[index];
            _locIndices.RemoveAt(index);
            locations.RemoveAt(locationIndex);
            // _locIndices列表里所有大于 locationIndex 的数据都要递减
            for (int i = 0; i < _locIndices.Count; i++)
            {
                int item = _locIndices[i];
                if (item > locationIndex)
                {
                    _locIndices[i] = item - 1;
                }
            }
        }
    }


    /// <summary>
    ///     保存符号在不同文件中的字符位置。
    ///     <list type="bullet">
    ///         <item>一般符号不允许重复声明，此表中只有一个是声明位置，其他的都是引用位置。</item>
    ///         <item>
    ///             如果是宏符号，由于宏定义允许使用 #undef 指令删除定义，在某些位置
    ///         </item>
    ///     </list>
    /// </summary>
    [DebuggerDisplay("Count = {Count}")]
    protected class SymbolLocationMap(ISymbol symbol) : IReadOnlyList<SyntaxLocation>
    {
        private readonly Dictionary<DocumentUri, FileLocationIndices> _fileIndexMap =
            new(DocumentUriComparer.Default);

        private readonly List<SyntaxLocation> _locations = [];

        public SyntaxLocation this[int index] =>
            index >= 0 && index < _locations.Count
                ? _locations[index]
                : throw new IndexOutOfRangeException(nameof(index));

        public int Count => _locations.Count;

        public IEnumerator<SyntaxLocation> GetEnumerator()
        {
            return _locations.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }


        /// <summary>
        ///     向列表末尾添加一个新的文本位置。
        ///     <list type="bullet">
        ///         <item>对于不区分文件的位置列表，直接添加到列表末尾</item>
        ///         <item>对于各个文件的位置表，会先检索插入位置，保证列表位置是递增的</item>
        ///     </list>
        /// </summary>
        public void AddLocation(in SyntaxLocation location)
        {
            if (!_fileIndexMap.TryGetValue(location.Uri, out FileLocationIndices? indices))
            {
                indices = new FileLocationIndices(location.Uri, _locations);
                _fileIndexMap.Add(location.Uri, indices);
            }

            indices.Add(location);
        }


        /// <summary>
        ///     将指定文本位置移除出集合，如果不存在，将不做任何操作。
        /// </summary>
        /// <param name="location">需要删除的语法位置</param>
        public void RemoveLocation(in SyntaxLocation location)
        {
            if (!_fileIndexMap.TryGetValue(location.Uri, out FileLocationIndices? indices))
            {
                return;
            }

            indices.Remove(location);
        }


        /// <summary>
        ///     将文本位置列表添加到当前表中
        /// </summary>
        public void AddLocations(IEnumerable<SyntaxLocation> locations)
        {
            foreach (SyntaxLocation loc in locations)
            {
                AddLocation(loc);
            }
        }


        /// <summary>
        ///     如果包含指定文件的位置信息，<see langword="true" />
        /// </summary>
        public bool FileExists(DocumentUri uri)
        {
            return _fileIndexMap.TryGetValue(uri, out FileLocationIndices? list) && list.Count > 0;
        }


        /// <summary>
        ///     如果包含指定文件的位置信息，<see langword="true" />
        /// </summary>
        public bool FileExists(string fileName)
        {
            return FileExists(DocumentUriCache.GetCached(fileName));
        }


        /// <summary>
        ///     获取已有的文本位置列表，包含声明和引用。
        /// </summary>
        public ImmutableArray<SyntaxLocation> GetLocations()
        {
            return [.. _locations];
        }


        /// <summary>
        ///     获取指定文件中的此符号的引用位置列表。
        /// </summary>
        /// <param name="uri">文件URI</param>
        public ImmutableArray<SyntaxLocation> GetLocationsOfFile(DocumentUri uri)
        {
            return _fileIndexMap.TryGetValue(uri, out FileLocationIndices? indices)
                ? indices.GetLocations()
                : ImmutableArray<SyntaxLocation>.Empty;
        }


        /// <summary>
        ///     获取指定文件中的此符号的引用位置列表。
        /// </summary>
        /// <param name="fileName">文件名，不区分大小写</param>
        public ImmutableArray<SyntaxLocation> GetLocationsOfFile(string fileName)
        {
            return GetLocationsOfFile(DocumentUriCache.GetCached(fileName));
        }


        /// <summary>
        ///     如果符号在指定文件中的最后一个位置是否已经是已声明的。
        /// </summary>
        /// <param name="uri">文件URI</param>
        public bool IsDeActiveInFile(DocumentUri uri)
        {
            if (_fileIndexMap.TryGetValue(uri, out FileLocationIndices? indices) && indices.Count > 0)
            {
                return !_locations[indices[^1]].IsActive;
            }

            return false;
        }


        /// <summary>
        ///     如果符号在指定文件中的最后一个位置是否已经是已声明的。
        /// </summary>
        /// <param name="fileName">文件名</param>
        public bool IsDeActiveInFile(string fileName)
        {
            return IsDeActiveInFile(DocumentUriCache.GetCached(fileName));
        }


        /// <summary>
        ///     如果符号在文件的某个位置是无效的，返回<see langword="true" />。
        ///     此方法针对宏符号（ #undef 取消宏定义 ），其他类型符号都会返回<see langword="false" />。
        /// </summary>
        /// <param name="uri">文件URI</param>
        /// <param name="position">文件中的字符位置</param>
        public bool IsDeActiveAtPositionOfFile(DocumentUri uri, int position)
        {
            if (!symbol.IsMacro)
            {
                return false;
            }

            // 1. 文件内没有引用，返回 false。
            // 2. 在扫描代码的过程中，查找到了此符号，表示此符号已经被引入到了当前文件中，
            //    此类中不需要考虑已有的其他定义位置，只要此宏没有在当前文件中被 #undef 指令删除，
            //    就可以返回 false。
            if (!_fileIndexMap.TryGetValue(uri, out FileLocationIndices? indices) ||
                !indices.ContainsDeactivate)
            {
                return false;
            }

            ImmutableArray<SyntaxLocation> locs = indices.GetLocations();
            int findIndex = CollectionHelper.BinarySearch(locs, position,
                (loc, pos) => loc.Start - pos);

            if (findIndex < 0)
            {
                findIndex = ~findIndex - 1;
            }

            return findIndex < locs.Length && !locs[findIndex].IsActive;
        }


        /// <summary>
        ///     如果符号在文件的某个位置是无效的，返回<see langword="true" />。
        ///     此方法针对宏符号（ #undef 取消宏定义 ），其他类型符号都会返回<see langword="false" />。
        /// </summary>
        /// <param name="fileName">查找的文件名</param>
        /// <param name="position">文件中的字符位置</param>
        public bool IsDeActiveAtPositionOfFile(string fileName, int position)
        {
            return IsDeActiveAtPositionOfFile(DocumentUriCache.GetCached(fileName), position);
        }
    }
}